/**
 * 
 */
package cz.cuni.mff.abacs.burglar.visual.play_state;

import com.aem.sticky.StickyListener;
import com.aem.sticky.button.Button;
import com.aem.sticky.button.TextButton;
import com.aem.sticky.button.events.SimpleClickListener;
import com.aem.sticky.dialog.Notification;
import cz.cuni.mff.abacs.burglar.logics.ExecutingMap;
import cz.cuni.mff.abacs.burglar.logics.Player;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseInterface;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseObject;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Agent;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Guard;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Inventory;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Item;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Active;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Lockable;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Vender;
import cz.cuni.mff.abacs.burglar.visual.VisualBurglar;
import java.util.ArrayList;
import java.util.List;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.state.StateBasedGame;



/**
 * Contains the user interface of the gameplay - buttons, 
 * keyboard and mouse input.
 * 
 * @author abacs
 *
 */
public abstract class InterfacedPlayState extends PlayState {
	
	
	/** Width of the buttons in pixels */
	public static final int BUTTON_WIDTH  = 100;
	/** Height of the buttons in pixels */
	public static final int BUTTON_HEIGHT = 21;
	/**  */
	public static final String STR_INVALID_GAME_MAP = "INVALID GAME MAP";
	
	
	protected static String STR_EXIT = "Exit";
	protected static String STR_LEAVE = "Leave";
	protected static String STR_PAUSE = "Pause";
	protected static String STR_CONTINUE = "Continue";
	protected static String STR_RESTART = "Restart";
	protected static String STR_SPEED_UP = "Speed Up";
	protected static String STR_SLOW_DOWN = "Slow Down";
	protected static String STR_INC_DISTANCE = "Inc Distance";
	protected static String STR_DEC_DISTANCE = "Dec Distance";

	protected static String STR_ACTIVATE = "Activate";
	protected static String STR_DEACTIVATE = "Deactivate";
	protected static String STR_CLOSE = "Close";
	protected static String STR_OPEN = "Open";
	protected static String STR_LOCK = "Lock";
	protected static String STR_UNLOCK = "Unlock";
	protected static String STR_CANCEL = "Cancel";
	
	protected static String STR_PICK_UP = "Pick Up";
	protected static String STR_TAKE_UNIFORM = "Take Uniform";
	
	protected static String STR_USE = "Use";
	
	protected static String STR_HIDE_INTENT = "Hide Intent";
	protected static String STR_SHOW_INTENT = "Show Intent";
	protected static String STR_DAZE = "Daze";
	
	protected static String STR_LOST_MESSAGE = "Level lost.";
	protected static String STR_SOLVED_MESSAGE = "Level solved.";
	protected static String STR_USED_PENALTY = " Used penalty points: ";
	
	protected static String SUBPATH_CLICK_SOUND = "click.ogg";
	
	// -------------------------------------------------------------------------
	
	
	/**  */
	private StickyListener _buttonListener = null;
	/** Button container drawn at the down right corner. */
	private ButtonPanel _buttonPanel = new ButtonPanel(this);
	/** Button container drawn at the place of the cursor. */
	private ContextMenu _contextMenu = new ContextMenu(this);
	
	private Sound _buttonSound = null;
	
	/**
	 * Notifications to the user.
	 */
	private Notification _notification = new Notification(0, 0, "", STR_CONTINUE);
	private int _currentNotification = -1;
	
	
	// -------------------------------------------------------------------------
	// Constructors:
	
	
	/** 
	 * 
	 * 
	 * @param stateID
	 * @throws SlickExpception
	 */
	protected InterfacedPlayState(int stateID)
			throws SlickException {
		super(stateID);
		
		this._notification.addListener(new SimpleClickListener() {
				@Override
				public void onClick() {
					showNextNotification();
				}
		});
	}
	
	
	// -------------------------------------------------------------------------
	// state methods:
	
	
	@Override
	public void init(GameContainer container, StateBasedGame game) 
		throws SlickException {
		super.init(container, game);
		
		// create the buttons if not yet initiated:
		// set up the buttons:
		this._buttonListener = new StickyListener();
		
		this._buttonSound = 
			new Sound(VisualBurglar.PATH_SOUNDS + SUBPATH_CLICK_SOUND);
		// buttons:
			
		this._buttonPanel.init(container, this._buttonListener, this._buttonSound);
		
		this._contextMenu.init(container, this._buttonListener, this._buttonSound);
		
		this._notification.init(container, _buttonListener, this._buttonSound);
		
		// calculate drawing canvas size:
		this._screen._height = 
			(container.getHeight() - 3 * BUTTON_HEIGHT) / 
			VisualBurglar.RESOURCE_BLOCK_SIZE;
	}
	
	
	/**
	 * Adds listeners.
	 */
	@Override
	public void enter(GameContainer container, StateBasedGame game) throws SlickException {
		super.enter(container, game);
		container.getInput().addListener(this._buttonListener);
		System.out.println("Play state entered");
		this._runingState = PlayState.RuningState.PAUSED;
		this._buttonPanel._pauseButton.setText(STR_CONTINUE);
	}
	
	
	/**
	 * Removes all listeners.
	 */
	@Override
	public void leave(GameContainer container, StateBasedGame game) throws SlickException {
		super.leave(container, game);
		container.getInput().removeListener(_buttonListener);
		this._currentNotification = 0;
		this._contextMenu.hide();
		System.out.println("Play state left");
	}
	
	
	@Override
	public void update(GameContainer container, StateBasedGame game, int delta)
		throws SlickException {
		// buttons:
		this._buttonPanel.update(container, delta);
		
		this._contextMenu.update(container, delta);
		
		this._notification.update(container, delta);
		
		super.update(container, game, delta);
		
		// set the message:
		
		if(
			this._map != null &&
			(
				this._notification.isVisible() == false ||
				this._currentNotification < this._map.getLevelNotes().size()
			)
		){
			switch(this._mapState){
			case LOST:
				this.createEndNotification(STR_LOST_MESSAGE + STR_USED_PENALTY + this._player.getPenalty());
				break;
			case SOLVED:
				this.createEndNotification(STR_SOLVED_MESSAGE + STR_USED_PENALTY + this._player.getPenalty());
				break;
			}
		}
	}
	
	
	/**
	 *  Allows to draw the world.
	 *  
	 *  @param container
	 *  @param game
	 *  @param graphics
	 *  @throws SlickException
	 *  */
	@Override
	public void render(
			GameContainer container,
			StateBasedGame game,
			Graphics graphics
	) throws SlickException {
		// buttons:		
		this._buttonPanel.render(container, graphics);
		
		this._contextMenu.render(container, graphics);
		
		this._notification.render(container, graphics);
		
		// HUD:
		this.drawHUD(container, graphics);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	@Override
	public void reload() {
		super.reload();
		
		this._player = new Player(this._map);
		
		this._currentNotification = 0;
	}
	
	
	@Override
	public void loadingFinished(ExecutingMap map, ExecutingMap secondaryMap) {
		super.loadingFinished(map, secondaryMap);
		
		this._player = new Player(this._map);
		
		this.createStartNotification();
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Draws text descriptions under the game area.
	 * 
	 * @param container
	 * @param graphics
	 */
	private void drawHUD(GameContainer container, Graphics graphics) {
		if(this._map == null)
			return;
		
		graphics.setColor(org.newdawn.slick.Color.red);
		
		graphics.drawLine(0,
				container.getHeight() - 3 * BUTTON_HEIGHT,
				container.getWidth(),
				container.getHeight() - 3 * BUTTON_HEIGHT
		);
		
		StringBuilder hud = new StringBuilder();
		
		hud.append('[');
		hud.append(this._mapState.toString());
		hud.append(", ");
		hud.append(this._runingState.toString());
		hud.append("] | ");
		
		hud.append("[penalties: ");
		hud.append(this._player.getPenalty());
		hud.append("] ");
		
		hud.append('[');
		hud.append(this._selection.getX());
		hud.append(", ");
		hud.append(this._selection.getY());
		hud.append("] ");
		
		hud.append("| ");
		
		try{		
			List<BaseObject> objects = 
				this._map.getObjectsOnPosition(
						this._selection.getX(),
						this._selection.getY()
				);
			for(BaseObject object : objects){
				
				hud.append(object.getType().toString());
				hud.append('(');
				hud.append(object.getId());
				hud.append(")");
				
				switch(object.getType()){
				case GUARD:
				case BURGLAR:
					hud.append("[{");
					hud.append(
							generateHUDItemObjects(
									((Inventory)object).getItems()
							)
					);
					hud.append("}]");
					
					break;
				case CONTAINER:
					hud.append("[locked: ");
					hud.append(((Lockable)object).isLocked());
					
					if(((Lockable)object).getKeyId() != -1){
						hud.append(", key: ");
						hud.append(((Lockable)object).getKeyId());
					}
					
					hud.append(", {");
					hud.append(
							generateHUDItemObjects(
									((Inventory)object).getItems()
							)
					);
					hud.append("}]");
					break;
				case DOOR:
					hud.append("[locked: ");
					hud.append(((Lockable)object).isLocked());
					
					if(((Lockable)object).getKeyId() != -1){
						hud.append(", key: ");
						hud.append(((Lockable)object).getKeyId());
					}
					hud.append("]");
					break;
				case CAMERA:
					hud.append("[active: ");
					hud.append(((Active)object).isActive());
					hud.append("]");
					break;
				default:
				}
				
				hud.append(", ");
			}
			
			if(objects.isEmpty() == false){
				hud.deleteCharAt(hud.length() - 1);
				hud.deleteCharAt(hud.length() - 1);
			}
		}catch(Exception e){
			// game map is invalid
			System.err.println(e.toString());
			hud.append(STR_INVALID_GAME_MAP);
		}
		
		// draw it:
		graphics.drawString(
				hud.toString(),
				5,
				container.getHeight() - 20
		);
		graphics.setColor(org.newdawn.slick.Color.white);
	}
	
	
	/**
	 * Generates a string of information about selected items.
	 * 
	 * @param itemsToDisplay objects to display information about.
	 * @return result string to display.
	 */
	private static String generateHUDItemObjects(List<Item> itemsToDisplay) {
		StringBuilder ret = new StringBuilder();
		for(Item item : itemsToDisplay){
			ret.append(item.getType().toString());
			ret.append('(');
			ret.append(item.getId());
			ret.append("), ");
		}
		
		if(ret.length() > 0){
			// remove the two last characters
			ret.deleteCharAt(ret.length() - 1);
			ret.deleteCharAt(ret.length() - 1);
		}
		
		return ret.toString();
	}
	
	
	// =========================================================================
	
	
	@Override
	public void mouseMoved(int oldX, int oldY, int newX, int newY){
		// if a context menu is opened, block the selection change.
		if(this._contextMenu.isVisible() == false)
			super.mouseMoved(oldX, oldY, newX, newY);
	}
	
	
	@Override 
	public void mouseClicked(int button, int x, int y, int clickCount) {
		super.mouseClicked(button, x, y, clickCount);
		
		// 0 - left
		// 1 - right
		// 2 - middle
		
		if(button == 0 && _contextMenu.isVisible() == false){
			this._contextMenu.show(x, y);
		}
	}
	
	
	@Override
	public void pauseUnpause() {
		super.pauseUnpause();
		if(this._runingState == RuningState.PAUSED)
			this._buttonPanel._pauseButton.setText(STR_CONTINUE);
		else
			this._buttonPanel._pauseButton.setText(STR_PAUSE);
	}
	
	
	/** Select an agent to visualize it's intents. */
	public void setIntentSubjectSelected() {
		int mapX = this._selection.getX();
		int mapY = this._selection.getY();
		
		Agent agent = null;
		
		for(BaseObject object : this._map.getObjectsOnPosition(mapX, mapY)){
			if(
				object.isTypeOf(BaseInterface.Type.GUARD) ||
				object.isTypeOf(BaseInterface.Type.BURGLAR)
			){
				agent = (Agent)object;
			}
		}
		
		this._player.selectAgent(agent);
	}
	
	
	/** Increases the visible viewing distance. */
	public void increaseIntentDistance() {
		this._player.increaseIntentDistance();
	}
	
	
	/** Decreases the visible viewing distance. */
	public void decreaseIntentDistance() {
		this._player.decreaseIntentDistance();
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Context menu object.
	 */
	private class ContextMenu {
		
		private InterfacedPlayState _parent;
		
		private boolean _isVisible = false;
		
		private TextButton _closeButton = null;
		private TextButton _lockButton = null;
		private TextButton _closeLockButton = null;
		private TextButton _activateButton = null;
		private TextButton _cancelButton = null;
		private TextButton _showIntentButton = null;
		private TextButton _dazeButton = null;
		
		
		private List<TextButton> _buttons = 
			new ArrayList<TextButton>(4);
		
		private List<BaseObject> _selectedObjects = null;
		
		
		// ---------------------------------------------------------------------
		// constructors:
		
		
		/**
		 * 
		 * @param parent
		 */
		protected ContextMenu(InterfacedPlayState parent) {
			this._parent = parent;
		}
		
		
		// ---------------------------------------------------------------------
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		public void init(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
		
			this.createActivateButton(container, listener, buttonSound);
			this.createCloseButton(container, listener, buttonSound);
			this.createLockButton(container, listener, buttonSound);
			this.createCloseLockButton(container, listener, buttonSound);
			this.createCancelButton(container, listener, buttonSound);
			this.createShowIntentButton(container, listener, buttonSound);
			this.createDazeButton(container, listener, buttonSound);
		}
		
		
		/**
		 * 
		 * @param container
		 * @param delta
		 * @throws SlickException
		 */
		public void update(
				GameContainer container,
				int delta
		) throws SlickException {
			// buttons:
			for(Button button : this._buttons){
				button.update(container, delta);
			}
		}
		
		
		/**
		 * 
		 * @param container
		 * @param graphics
		 * @throws SlickException
		 */
		public void render(
			GameContainer container,
			Graphics graphics
		) throws SlickException {
			
			// draw background:
/*			Color color = graphics.getColor();
			graphics.setColor(Color.orange);
			graphics.fillRect(
					0,                    container.getHeight() - 3 * BUTTON_HEIGHT,
					container.getWidth(), 3 * BUTTON_HEIGHT
			);
			graphics.setColor(color);
			*/
			
			// draw buttons:
			for(TextButton button : this._buttons){
				button.render(container, graphics);
			}
			
		}
		
		
		// ---------------------------------------------------------------------
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createActivateButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = 
					STR_ACTIVATE + " (" + Player.PENALTY_OBJECT_ACTIVATED + ")";
			
			this._activateButton = 
					new TextButton(x, y, buttonText, buttonSound);
			
			this._activateButton.setColor(Color.magenta);
			
			listener.add(this._activateButton);
			
			this._activateButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.activateDeactivateSelection();
					hide();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createCloseButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = 
					STR_CLOSE + " (" + Player.PENALTY_OBJECT_CLOSED + ")";
			
			this._closeButton = 
					new TextButton(x, y, buttonText, buttonSound);
			
			this._closeButton.setColor(Color.magenta);
			
			listener.add(this._closeButton);
			
			this._closeButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.closeOpenSelection();
					hide();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createLockButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = 
					STR_LOCK + " (" + Player.PENALTY_OBJECT_LOCKED + ")";
			
			this._lockButton = new TextButton(x, y, buttonText, buttonSound);
			
			this._lockButton.setColor(Color.magenta);
			
			listener.add(this._lockButton);
			
			this._lockButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.lockUnlockSelection();
					hide();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createCloseLockButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = 
					STR_CLOSE + " + " + STR_LOCK + 
					" (" + (Player.PENALTY_OBJECT_CLOSED + Player.PENALTY_OBJECT_LOCKED) + ")";
			
			this._closeLockButton = new TextButton(x, y, buttonText, buttonSound);
			
			this._closeLockButton.setColor(Color.magenta);
			
			listener.add(this._closeLockButton);
			
			this._closeLockButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.closeOpenSelection();
					_parent.lockUnlockSelection();
					hide();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createCancelButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = STR_CANCEL;
			
			this._cancelButton = 
					new TextButton(x, y, buttonText, buttonSound);
			
			this._cancelButton.setColor(Color.magenta);
			
			listener.add(this._cancelButton);
			
			this._cancelButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					hide();
				}
			});
		}
	
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createShowIntentButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = 
					STR_SHOW_INTENT + " (" + Player.PENALTY_INTENT_SHOW + ")";
			
			this._showIntentButton = 
					new TextButton(x, y, buttonText, buttonSound);
			
			this._showIntentButton.setColor(Color.magenta);
			
			listener.add(this._showIntentButton);
			
			this._showIntentButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					setIntentSubjectSelected();
					hide();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createDazeButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = - BUTTON_WIDTH;
			float y = - BUTTON_HEIGHT;
			
			String buttonText = 
					STR_DAZE + " (" + Player.PENALTY_AGENT_STUNNED + ")";
			
			this._dazeButton = 
					new TextButton(x, y, buttonText, buttonSound);
			
			this._dazeButton.setColor(Color.magenta);
			
			listener.add(this._dazeButton);
			
			this._dazeButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					dazeSelected();
					hide();
				}
			});
		}
		
		
		// ---------------------------------------------------------------------
		
		
		/**
		 * Draws the relevant context menu on a selected screen position.
		 * 
		 * @param screenX
		 * @param screenY
		 */
		private void show(float screenX, float screenY) {
			int mapX = this._parent._selection.getX();
			int mapY = this._parent._selection.getY();
			
			// hide the old context menu:
			this.hide();
			
			try{
				this._selectedObjects = this._parent._map.getObjectsOnPosition(mapX, mapY);
				for(BaseObject object : this._selectedObjects){
					switch(object.getType()){
					case CONTAINER:
					case DOOR:
						Lockable lockable = (Lockable)object;
						if(lockable.isClosed()){
							// it can be opened, locked, or unlocked:
							if(lockable.isLocked()){
								// it can be only unlocked:
								this._lockButton.setText(STR_UNLOCK + " (" + Player.PENALTY_OBJECT_LOCKED + ")");
								this._buttons.add(this._lockButton);
							}else{
								// it can be locked, or opened:
								this._lockButton.setText(STR_LOCK + " (" + Player.PENALTY_OBJECT_LOCKED + ")");
								this._buttons.add(this._lockButton);
								this._closeButton.setText(STR_OPEN + " (" + Player.PENALTY_OBJECT_CLOSED + ")");
								this._buttons.add(this._closeButton);
							}
						}else{
							// it can be closed:
							this._closeButton.setText(STR_CLOSE + " (" + Player.PENALTY_OBJECT_CLOSED + ")");
							this._buttons.add(this._closeButton);
							// it can be closed and locked:
							this._buttons.add(this._closeLockButton);
						}
						break;
					case CAMERA:
					case PHONE:
					case SWITCH:
						if(((Active)object).isActive())
							this._activateButton.setText(STR_DEACTIVATE + " (" + Player.PENALTY_OBJECT_ACTIVATED + ")");
						else
							this._activateButton.setText(STR_ACTIVATE + " (" + Player.PENALTY_OBJECT_ACTIVATED + ")");
						this._buttons.add(this._activateButton);
						break;
					case VENDER:
						if(((Vender)object).hasDropped() == false){
							this._activateButton.setText(STR_ACTIVATE + " (" + Player.PENALTY_VENDER_ACTIVATED + ")");
							this._buttons.add(this._activateButton);
						}
						break;
					case GUARD:
						Guard guard = (Guard)object;
						if(guard.isInState(Guard.State.WELL)){
							this._buttons.add(this._dazeButton);
						}
					case BURGLAR:
						if(this._parent._player.getSelectedAgent() == (Agent)object){
							this._showIntentButton.setText(STR_HIDE_INTENT);
						}else{
							this._showIntentButton.setText(STR_SHOW_INTENT + " (" + Player.PENALTY_INTENT_SHOW + ")");
						}
						this._buttons.add(this._showIntentButton);
						break;
					default:
					}
				}
			}catch(Exception e){
				System.err.println(e.toString());
			}
			
			// add an exit button if there are any other buttons already
			if(this._buttons.size() > 0)
				this._buttons.add(this._cancelButton);
			else
				return;
			
			int deltaX = BUTTON_WIDTH / 4;
			int deltaY = 0;
			
			// if the menu does not fit to the screen,
			// place it to the opposite side.
			
			int screenCornerX = 
				this._parent._screen.mapXToScreenX(this._parent._screen.getWidth());
			int screenCornerY = 
				this._parent._screen.mapYToScreenY(this._parent._screen.getHeight());
			
			if(screenX > screenCornerX - BUTTON_WIDTH * 2)
				deltaX = - BUTTON_WIDTH;
			if(screenY > screenCornerY - BUTTON_HEIGHT * this._buttons.size())
				deltaY = - BUTTON_HEIGHT * this._buttons.size();
			
			for(TextButton button : this._buttons){
				button.moveTo(screenX + deltaX, screenY + deltaY);
				deltaY += BUTTON_HEIGHT;
			}
			
			this._isVisible = true;
		}
		
		
		private void hide() {
			this._selectedObjects = null;
			for(TextButton button : this._buttons)
				button.hide();
			this._buttons.clear();
			this._isVisible = false;
		}
		
		
		private boolean isVisible() {
			return this._isVisible;
		}
		
		
		private void dazeSelected() {
			if(this._selectedObjects == null)
				return;
			for(BaseObject object : this._selectedObjects){
				if(object.isTypeOf(BaseInterface.Type.GUARD))
					this._parent._player.dazeAgent((Guard)object);
			}
		}
		
		
		private void setIntentSubjectSelected() {
			if(this._selectedObjects == null)
				return;
			for(BaseObject object : this._selectedObjects){
				if(object.isTypeOf(BaseInterface.Type.GUARD) || object.isTypeOf(BaseInterface.Type.BURGLAR))
					this._parent._player.selectAgent((Agent)object);
			}
		}
		
	}
	
	
	/**
	 * Holds the buttons on the lower part of the window.
	 */
	private class ButtonPanel {
		
		private InterfacedPlayState _parent;
		
		// buttons:
		private Button _exitButton = null;
		private Button _leaveButton = null;
		private Button _restartButton = null;
		private Button _slowDownButton = null;
		private Button _speedUpButton = null;
		
		private Button _incIntentDistanceButton = null;
		private Button _decIntentDistanceButton = null;
		
		protected TextButton _pauseButton = null;
		
		
		// ---------------------------------------------------------------------
		// constructors:
		
		
		/**
		 * 
		 * @param parent
		 */
		protected ButtonPanel(InterfacedPlayState parent) {
			this._parent = parent;
		}
		
		
		// ---------------------------------------------------------------------
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		public void init(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			this.createExitButton(container, listener, buttonSound);
			this.createLeaveButton(container, listener, buttonSound);
			this.createRestartButton(container, listener, buttonSound);
			this.createSlowDownButton(container, listener, buttonSound);
			this.createSpeedUpButton(container, listener, buttonSound);
			
			this.createPauseButton(container, listener, buttonSound);
			
			this.createIncIntentDistanceButton(container, listener, buttonSound);
			this.createDecIntentDistanceButton(container, listener, buttonSound);
		}
		
		
		/**
		 * 
		 * @param container
		 * @param delta
		 * @throws SlickException
		 */
		public void update(GameContainer container, int delta)
			throws SlickException {
			
			this._exitButton.update(container, delta);
			this._leaveButton.update(container, delta);
			this._restartButton.update(container, delta);
			this._slowDownButton.update(container, delta);
			this._speedUpButton.update(container, delta);
			this._pauseButton.update(container, delta);
			
			this._incIntentDistanceButton.update(container, delta);
			this._decIntentDistanceButton.update(container, delta);
		}
		
		
		/**
		 *  Allows to draw the world.
		 *  */
		public void render(
				GameContainer container,
				Graphics graphics
		) throws SlickException {
			
			this._exitButton.render(container, graphics);
			this._leaveButton.render(container, graphics);
			this._restartButton.render(container, graphics);
			this._slowDownButton.render(container,graphics);
			this._speedUpButton.render(container, graphics);
			this._pauseButton.render(container, graphics);
			
			this._incIntentDistanceButton.render(container, graphics);
			this._decIntentDistanceButton.render(container, graphics);
		}
		
		
		// ---------------------------------------------------------------------
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createExitButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = container.getWidth() - 1 * BUTTON_WIDTH;
			float y = container.getHeight() - 2 * BUTTON_HEIGHT;
			
			this._exitButton = new TextButton(x, y, InterfacedPlayState.STR_EXIT, buttonSound);
			
			listener.add(this._exitButton);
			
			this._exitButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent._container.exit();
				}	
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createLeaveButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = container.getWidth() - 1 * BUTTON_WIDTH;
			float y = container.getHeight() - 3 * BUTTON_HEIGHT;
			
			this._leaveButton = 
					new TextButton(x, y, InterfacedPlayState.STR_LEAVE, buttonSound);
			
			listener.add(this._leaveButton);
			
			this._leaveButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent._stateBasedGame.enterState(VisualBurglar.STATE_MENU);
				}
			});	
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createRestartButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = container.getWidth() - 2 * BUTTON_WIDTH;
			float y = container.getHeight() -  2 * BUTTON_HEIGHT;
			
			this._restartButton = new TextButton(x, y, STR_RESTART, buttonSound);
			
			
			listener.add(this._restartButton);
			
			this._restartButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.reload();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createSlowDownButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {

			float x = container.getWidth() - 3 * BUTTON_WIDTH;
			float y = container.getHeight() - 2 * BUTTON_HEIGHT;
			
			this._slowDownButton = 
					new TextButton(x, y, STR_SLOW_DOWN, buttonSound);
			
			listener.add(this._slowDownButton);
			
			this._slowDownButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.decreaseSpeed();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createSpeedUpButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {

			float x = container.getWidth() - 3 * BUTTON_WIDTH;
			float y = container.getHeight() - 3 * BUTTON_HEIGHT;
			
			this._speedUpButton = 
					new TextButton(x, y, STR_SPEED_UP, buttonSound);
			
			listener.add(this._speedUpButton);
			
			this._speedUpButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.increaseSpeed();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createPauseButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {

			float x = container.getWidth() - 2 * BUTTON_WIDTH;
			float y = container.getHeight() - 3 * BUTTON_HEIGHT;
			
			this._pauseButton = 
					new TextButton(x, y, STR_CONTINUE, buttonSound);
			
			listener.add(this._pauseButton);
			
			this._pauseButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.pauseUnpause();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createIncIntentDistanceButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {

			float x = container.getWidth() - 5 * BUTTON_WIDTH;
			float y = container.getHeight() - 3 * BUTTON_HEIGHT;
			
			String buttonText = 
					STR_INC_DISTANCE + " (" + Player.PENALTY_INTENT_INCREASED + ")";
			
			this._incIntentDistanceButton = 
					new TextButton(x, y, buttonText, buttonSound);
			
			listener.add(this._incIntentDistanceButton);
			
			this._incIntentDistanceButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.increaseIntentDistance();
				}
			});
		}
		
		
		/**
		 * 
		 * @param container
		 * @param listener
		 * @param buttonSound
		 * @throws SlickException
		 */
		private void createDecIntentDistanceButton(
				GameContainer container,
				StickyListener listener,
				Sound buttonSound
		) throws SlickException {
			
			float x = container.getWidth() - 5 * BUTTON_WIDTH;
			float y = container.getHeight() - 2 * BUTTON_HEIGHT;
			
			this._decIntentDistanceButton = 
					new TextButton(x, y, STR_DEC_DISTANCE, buttonSound);
			
			listener.add(this._decIntentDistanceButton);
			
			this._decIntentDistanceButton.addListener(new SimpleClickListener() {
				
				@Override
				public void onClick() {
					_parent.decreaseIntentDistance();
				}
			});
		}
		
		
	}
	
	
	/**
	 * 
	 * @param notificationText 
	 */
	private void createEndNotification(String notificationText) {
		
		this._notification.hide();
		
		this._notification.moveTo(
				this._container.getWidth() / 2,
				this._container.getHeight() / 2
		);
		
		this._notification.setBodyText(notificationText);
		this._notification.show();
	}
	
	
	/**
	 * 
	 */
	private void createStartNotification() {
		// the other notification should be hidden:
		this._notification.hide();
		
		this._currentNotification = 0;
		
		List<String> notes = this._map.getLevelNotes();
		if(notes.isEmpty())
			return;
		
		this._notification.moveTo(
				this._container.getWidth() / 2,
				this._container.getHeight() / 2
		);
		
		this._notification.setBodyText(notes.get(0));
		this._notification.show();
	}
	
	
	/**
	 * Displays the next notification if there is any left, or leaves the play state
	 * if finished notification was clicked.
	 */
	private void showNextNotification() {
		
		if(
			this._mapState == PlayState.MapState.LOST ||
			this._mapState == PlayState.MapState.SOLVED
		){
			this._stateBasedGame.enterState(VisualBurglar.STATE_MENU);
		}
		
		// the level is not yet finished:
		
		List<String> notes = this._map.getLevelNotes();
		
		if(this._currentNotification + 1 < notes.size()){
			this._notification.setBodyText(notes.get(++this._currentNotification));
			this._notification.show();
		}
	}
	
	
}
